import { fabric } from 'fabric';

const DIVISOR = {
    rect: 1,
    circle: 2,
    triangle: 1
};
const DIMENSION_KEYS = {
    rect: {
        w: 'width',
        h: 'height'
    },
    circle: {
        w: 'rx',
        h: 'ry'
    },
    triangle: {
        w: 'width',
        h: 'height'
    }
};

/**
 * Set the start point value to the shape object
 * @param {fabric.Object} shape - Shape object
 * @ignore
 */
function setStartPoint(shape) {
    const originX = shape.getOriginX();
    const originY = shape.getOriginY();
    const originKey = originX.substring(0, 1) + originY.substring(0, 1);

    shape.startPoint = shape.origins[originKey];
}

/**
 * Get the positions of ratated origin by the pointer value
 * @param {{x: number, y: number}} origin - Origin value
 * @param {{x: number, y: number}} pointer - Pointer value
 * @param {number} angle - Rotating angle
 * @returns {object} Postions of origin
 * @ignore
 */
function getPositionsOfRotatedOrigin(origin, pointer, angle) {
    const sx = origin.x;
    const sy = origin.y;
    const px = pointer.x;
    const py = pointer.y;
    const r = (angle * Math.PI) / 180;
    const rx = (px - sx) * Math.cos(r) - (py - sy) * Math.sin(r) + sx;
    const ry = (px - sx) * Math.sin(r) + (py - sy) * Math.cos(r) + sy;

    return {
        originX: sx > rx ? 'right' : 'left',
        originY: sy > ry ? 'bottom' : 'top'
    };
}

/**
 * Whether the shape has the center origin or not
 * @param {fabric.Object} shape - Shape object
 * @returns {boolean} State
 * @ignore
 */
function hasCenterOrigin(shape) {
    return shape.getOriginX() === 'center' && shape.getOriginY() === 'center';
}

/**
 * Adjust the origin of shape by the start point
 * @param {{x: number, y: number}} pointer - Pointer value
 * @param {fabric.Object} shape - Shape object
 * @ignore
 */
function adjustOriginByStartPoint(pointer, shape) {
    const centerPoint = shape.getPointByOrigin('center', 'center');
    const angle = -shape.getAngle();
    const originPositions = getPositionsOfRotatedOrigin(centerPoint, pointer, angle);
    const originX = originPositions.originX;
    const originY = originPositions.originY;
    const origin = shape.getPointByOrigin(originX, originY);
    const left = shape.getLeft() - (centerPoint.x - origin.x);
    const top = shape.getTop() - (centerPoint.x - origin.y);

    shape.set({
        originX,
        originY,
        left,
        top
    });

    shape.setCoords();
}

/**
 * Adjust the origin of shape by the moving pointer value
 * @param {{x: number, y: number}} pointer - Pointer value
 * @param {fabric.Object} shape - Shape object
 * @ignore
 */
function adjustOriginByMovingPointer(pointer, shape) {
    const origin = shape.startPoint;
    const angle = -shape.getAngle();
    const originPositions = getPositionsOfRotatedOrigin(origin, pointer, angle);
    const originX = originPositions.originX;
    const originY = originPositions.originY;

    shape.setPositionByOrigin(origin, originX, originY);
}

/**
 * Adjust the dimension of shape on firing scaling event
 * @param {fabric.Object} shape - Shape object
 * @ignore
 */
function adjustDimensionOnScaling(shape) {
    const type = shape.type;
    const dimensionKeys = DIMENSION_KEYS[type];
    const scaleX = shape.scaleX;
    const scaleY = shape.scaleY;
    let width = shape[dimensionKeys.w] * scaleX;
    let height = shape[dimensionKeys.h] * scaleY;

    if (shape.isRegular) {
        const maxScale = Math.max(scaleX, scaleY);

        width = shape[dimensionKeys.w] * maxScale;
        height = shape[dimensionKeys.h] * maxScale;
    }

    const options = {
        hasControls: false,
        hasBorders: false,
        scaleX: 1,
        scaleY: 1
    };

    options[dimensionKeys.w] = width;
    options[dimensionKeys.h] = height;

    shape.set(options);
}

/**
 * Adjust the dimension of shape on firing mouse move event
 * @param {{x: number, y: number}} pointer - Pointer value
 * @param {fabric.Object} shape - Shape object
 * @ignore
 */
function adjustDimensionOnMouseMove(pointer, shape) {
    const origin = shape.startPoint;
    const type = shape.type;
    const divisor = DIVISOR[type];
    const dimensionKeys = DIMENSION_KEYS[type];
    const strokeWidth = shape.strokeWidth;
    const isTriangle = !!(shape.type === 'triangle');
    const options = {};
    let width = Math.abs(origin.x - pointer.x) / divisor;
    let height = Math.abs(origin.y - pointer.y) / divisor;

    if (width > strokeWidth) {
        width -= strokeWidth / divisor;
    }

    if (height > strokeWidth) {
        height -= strokeWidth / divisor;
    }

    if (shape.isRegular) {
        width = height = Math.max(width, height);

        if (isTriangle) {
            height = (Math.sqrt(3) / 2) * width;
        }
    }

    options[dimensionKeys.w] = width;
    options[dimensionKeys.h] = height;

    shape.set(options);
}

export default {
    /**
     * Set each origin value to shape
     * @param {fabric.Object} shape - Shape object
     */
    setOrigins(shape) {
        const leftTopPoint = shape.getPointByOrigin('left', 'top');
        const rightTopPoint = shape.getPointByOrigin('right', 'top');
        const rightBottomPoint = shape.getPointByOrigin('right', 'bottom');
        const leftBottomPoint = shape.getPointByOrigin('left', 'bottom');

        shape.origins = {
            lt: leftTopPoint,
            rt: rightTopPoint,
            rb: rightBottomPoint,
            lb: leftBottomPoint
        };
    },

    /**
     * Resize the shape
     * @param {fabric.Object} shape - Shape object
     * @param {{x: number, y: number}} pointer - Mouse pointer values on canvas
     * @param {boolean} isScaling - Whether the resizing action is scaling or not
     */
    resize(shape, pointer, isScaling) {
        if (hasCenterOrigin(shape)) {
            adjustOriginByStartPoint(pointer, shape);
            setStartPoint(shape);
        }

        if (isScaling) {
            adjustDimensionOnScaling(shape, pointer);
        } else {
            adjustDimensionOnMouseMove(pointer, shape);
        }

        adjustOriginByMovingPointer(pointer, shape);
    },

    /**
     * Adjust the origin position of shape to center
     * @param {fabric.Object} shape - Shape object
     */
    adjustOriginToCenter(shape) {
        const centerPoint = shape.getPointByOrigin('center', 'center');
        const originX = shape.getOriginX();
        const originY = shape.getOriginY();
        const origin = shape.getPointByOrigin(originX, originY);
        const left = shape.getLeft() + (centerPoint.x - origin.x);
        const top = shape.getTop() + (centerPoint.y - origin.y);

        shape.set({
            hasControls: true,
            hasBorders: true,
            originX: 'center',
            originY: 'center',
            left,
            top
        });

        shape.setCoords(); // For left, top properties
    }
};
